﻿using System;
using System.Collections.Generic;

using Storyteller.Framework.Graphics.Particles.Modifiers;
using Storyteller.Framework.Graphics.Particles.EmitModifiers;
using Storyteller.Framework.Math;

namespace Storyteller.Framework.Graphics.Particles.Emitters
{
	public enum ParticleBlendMode
	{
		Alpha,
		Additive
	}

	public abstract class ParticleEmitter
	{
		public float WorldRotation { get; set; }
		public Vector2 WorldPosition { get; set; }

		public float LocalRotation { get; set; }
		public Vector2 LocalPosition { get; set; }

		public ParticleBlendMode ParticleBlendMode { get; set; }
		public Texture Texture { get; set; }
		public TextureRegion TextureRegion { get; set; }
		public bool UseTextureRegion { get; set; }

		public int ParticlesPerSecond { get; set; }

		public float Duration { get; set; }
		public float InitialSpeed { get; set; }
		public float InitialScale { get; set; }
		public float InitialRotation { get; set; }
		public Color InitialColor { get; set; }

		public bool IsSingleFire { get; protected set; }
		public bool HasSingleFired { get; protected set; }

		public int ActiveParticleCount { get { return activeParticleCount; } }

		public bool Enabled { get; set; }

		public List<ParticleModifier> Modifiers { get { return modifiers; } set { modifiers = value; } }
		public List<ParticleEmitModifier> EmitModifiers { get { return emitModifiers; } set { emitModifiers = value; } }

		public ParticleEmitter(int maximumParticles, int particlesPerSecond, Texture texture)
		{
			this.maximumParticles = maximumParticles;
			particles = new Particle[maximumParticles];
			for (int particleIndex = 0; particleIndex < maximumParticles; particleIndex++)
				particles[particleIndex] = new Particle();

			Texture = texture;
			UseTextureRegion = false;
			ParticlesPerSecond = particlesPerSecond;

			ParticleBlendMode = ParticleBlendMode.Additive;
			Duration = 1;
			InitialSpeed = 0;
			InitialScale = 1;
			InitialRotation = 0;
			InitialColor = Color.White;

			random = RandomUtil.Random;

			Enabled = true;
		}

		public ParticleEmitter(int maximumParticles, Texture texture)
			: this(maximumParticles, 0, texture)
		{
			IsSingleFire = true;
			HasSingleFired = false;
		}

		public ParticleEmitter(int maximumParticles, int particlesPerSecond, Texture texture, TextureRegion textureRegion)
		{
			this.maximumParticles = maximumParticles;
			particles = new Particle[maximumParticles];
			for (int particleIndex = 0; particleIndex < maximumParticles; particleIndex++)
				particles[particleIndex] = new Particle();

			Texture = texture;
			TextureRegion = textureRegion;
			UseTextureRegion = true;
			ParticlesPerSecond = particlesPerSecond;

			ParticleBlendMode = ParticleBlendMode.Additive;
			Duration = 1;
			InitialSpeed = 0;
			InitialScale = 1;
			InitialRotation = 0;
			InitialColor = Color.White;

			random = RandomUtil.Random;

			Enabled = true;
		}

		public ParticleEmitter(int maximumParticles, Random random, Texture texture, TextureRegion textureRegion)
			: this(maximumParticles, 0, texture, textureRegion)
		{
			IsSingleFire = true;
			HasSingleFired = false;
		}

		void EmitParticles(float deltaSeconds)
		{
			float timePerParticle = 1.0f / ParticlesPerSecond;

			float totalTime = timeRemaining + deltaSeconds;
			int count = (int)(totalTime / timePerParticle);
			timeRemaining = totalTime - (count * timePerParticle);

			count = System.Math.Min(count, maximumParticles - activeParticleCount);
			EmitParticles(count, particles, activeParticleCount);

			foreach (ParticleEmitModifier emitModifier in emitModifiers)
			{
				emitModifier.Process(particles, activeParticleCount, count);
			}

			activeParticleCount += count;
		}

		public void EmitOneShotParticles()
		{
			HasSingleFired = true;
			EmitParticles(maximumParticles, particles, 0);
			foreach (ParticleEmitModifier emitModifier in emitModifiers)
			{
				emitModifier.Process(particles, 0, maximumParticles);
			}
			activeParticleCount = maximumParticles;
		}

		protected abstract void EmitParticles(int count, Particle[] destination, int offset);

		public void Update(float deltaSeconds)
		{
			float deltaLifetime = deltaSeconds / Duration;

			for (int particleIndex = 0; particleIndex < activeParticleCount; particleIndex++)
			{
				Particle p = particles[particleIndex];
				p.Lifetime += deltaLifetime;

				if (p.Lifetime >= 1)
				{
					Particle temp = particles[particleIndex];
					particles[particleIndex] = particles[activeParticleCount - 1];
					particles[activeParticleCount - 1] = temp;
					particleIndex--;
					activeParticleCount--;
					continue;
				}

				p.Position.X += p.Velocity.X * deltaSeconds;
				p.Position.Y += p.Velocity.Y * deltaSeconds;

			}

			if (Enabled && !IsSingleFire)
				EmitParticles(deltaSeconds);

			foreach (ParticleModifier modifier in modifiers)
			{
				modifier.Process(deltaSeconds, particles, activeParticleCount);
			}
		}

		public void Draw(SpriteBatch spriteBatch, Matrix4 cameraTransform)
		{
			spriteBatch.Begin(ParticleBlendMode == ParticleBlendMode.Alpha ? BlendMode.Alpha : BlendMode.Additive, cameraTransform);
			for (int particleIndex = 0; particleIndex < activeParticleCount; particleIndex++)
			{
				Particle p = particles[particleIndex];
				if (UseTextureRegion)
					spriteBatch.Draw(Texture, TextureRegion, p.Position, p.Color, p.Rotation, new Vector2(p.Scale), TextureEffects.None, Matrix4.Identity);
				else
					spriteBatch.Draw(Texture, p.Position, null, p.Color, p.Rotation, new Vector2(0.5f), new Vector2(p.Scale), TextureEffects.None, Matrix4.Identity);
			}
			spriteBatch.End();
		}

		public void Reset()
		{
			activeParticleCount = 0;
			timeRemaining = 0;
		}

		protected Random random;

		float timeRemaining = 0;
		int activeParticleCount = 0;
		Particle[] particles;

		List<ParticleModifier> modifiers = new List<ParticleModifier>();
		List<ParticleEmitModifier> emitModifiers = new List<ParticleEmitModifier>();

		protected int maximumParticles;
	}
}
